// CDN_WEB_SRV_Extensions.h
//
// Copyright (c) 2011 LRUnit, LLC All Rights Reserved.
// Author : Kim McKinley
//
// This product includes software developed by :
//  LRUnit, LLC
//  Sqlite (http://www.sqlite.org)
//
// This software is under the terms of the MIT License :
/*
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
*/

#if !defined(CDN_WEB_SRV_Extensions_h)
#define CDN_WEB_SRV_Extensions_h

namespace cdn {
  namespace web {
    namespace srv {

// **********************************************************************
/// Web Server Extensions
/**
  This server class is used as the base class to extend functionality of the server.\n\n

  Derive from this class and create a BuildContent() function.\n
    In your BuildContent() function check the path to see if it is for your extension.\n
      if it is not a path you process, return a -1 to continue to the next extension\n
      if you do process the path\n
        place the result in the http.content() stringstream\n
        place the HTTP content type and possibly other HTTP headers in contenttype\n
        return with the HTML code: 200 for OK, 404 for Not Found, etc.\n
  Add your new extension to the ProcessRequest() function in CDN_WEB_SRV_Handler.h\n\n

  Use some of the extensions already written as a guide\n
*/
class ExtendServer
{
protected:
  msg::Http &http;

public:
  /// Constructor contains reference to the HTTP request and response
  ExtendServer(msg::Http &httpmsg) : http(httpmsg) {}

  /// Derived classes return the HTTP status code or -1 indicating to move to the next server extension
  // List of status codes can be found at http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
  virtual int BuildContent() = 0;
};

// **********************************************************************
//
// **********************************************************************
/// Simple server extension to re-show the console window if it has been hidden with the .hide command
class ShowConsole : public ExtendServer
{
public:
  ShowConsole(msg::Http &http) : ExtendServer(http) {}

  virtual int BuildContent()
  {
    if ( http.path() == "/ShowConsole" )
    {
      ShowWindow( GetConsoleWindow(), SW_SHOW );
      http.content() << "<html><body><h3>Show console done<h3></body></html>";
      http.contentType("text/html");
      return 200;
    }

    return -1;
  }
};


// **********************************************************************
//
// **********************************************************************
/// Handles HTML pages and images
/**
  This extension normally runs first. It looks for a filename of the path in the
  Url request.  If found it sends the file - if not it returns -1 for the server
  to continue with the next server extension.
*/
class FileContent : public ExtendServer
{
public:
  FileContent(msg::Http &http) : ExtendServer(http) {}

  virtual int BuildContent()
  {
    // Default result is to continue to the next extension
    int result = -1;

    // All filename paths are relative to the working directory
    std::string filename = std::string(".") + http.path();

    // Return Forbidden if path attempting to move up to a parent directory
    if ( filename.find("..") != std::string::npos )
    {
      http.content() << "<html><body><h2>Access forbidden.</h2></body></html>" << std::endl;
      return 403;
    }

    // See if we have the file requested
    std::ifstream requestedcontent;
    requestedcontent.open(filename.c_str(), std::fstream::in | std::ios::binary);
    // Nope - so return and continue to the next server extension
    if ( requestedcontent.fail() )
      return result;

    // Get the file attributes so we can see the size and modified time
    struct stat attrib;
    stat(filename.c_str(), &attrib);
    std::string LastModified = http.Rfc1123_DateTime(attrib.st_mtime);

    // Let the browser know the Last-Modified date/time
    http.responseHeader("Last-Modified", LastModified);

    // If the request contains a matching If-Modified-Since date/time - send Not Modified
    if ( http.requestHeader("If-Modified-Since") == LastModified )
      result = 304;
    else // Need to get the data from the file
    {
      http.content() << requestedcontent.rdbuf();
      result = 200;   // Result is 'OK'
    }

    // Determine the MIME from the file extension (ie: content type) and add to response header
    // If we can't will be automatically defaulted to 'text/plain'
    size_t pos;
    if ( (pos = filename.find_last_of(".")) != std::string::npos )
    {
      options::iterator c;
      std::string ext = filename.substr(pos+1);
      // Make the extension uppercase - and look for it in the MIME list
      std::transform(ext.begin(), ext.end(), ext.begin(), (int(*)(int)) toupper);
      if ( (c = gConfig["MIME"].find(ext)) != gConfig["MIME"].end() )
        http.contentType(c->second);
    }

  return result;
  }
};

// **********************************************************************
//
// **********************************************************************
/// Handles SQLite queries returning a HTML Div/Table structure for display within web pages
class SQLiteHtml : public ExtendServer
{
public:
  SQLiteHtml(msg::Http &http) : ExtendServer(http) {}

  /// Construct the content of the HTML table with SQLite data
  virtual int BuildContent()
  {
    if ( http.path() == "/QueryAjax" )
    {
        QuerySql();
        http.contentType("text/html");
        return 200;
    }

    return -1;
    
  }

  /// Run SQLite command(s) and return any rows as a HTML table
  void QuerySql()
  {
    db::Sqlite sqlite;

    http.content() << "<div class='SQLiteResult'>\n";

    // Open the database and execute the command returning the HTML results as http.content()
    try
    {
      sqlite.Open(http.req.params_["Database"]);
      sqlite.LoadRecordsIntoHtmlStream(http.req.params_["Command"], http.content() );
    }
    catch (std::exception& e) // SQLite returned an error
    {
      http.content() << "<div class='SQLiteError'>" << std::endl <<
        "<h3>SQLite database error : " << e.what() << "</h3></div>" << std::endl;
    }

    http.content() << "</div>\n";
  }
};

// **********************************************************************
//
// **********************************************************************
/// SQLite Xml Web Extension
/**
  This web server processes a HTML GET request that contains Sqlite SQL command(s).
  The response is a XML formatted reply.  The format of the request and reply is 
  documented in 'SQLiteToXMLDataExchange.html' for your reading enjoyment.
*/
using namespace xmlw;

class SQLiteXml : public ExtendServer
{
public:
  SQLiteXml(msg::Http &http) : ExtendServer(http) {}

  /// Construct the content of the XML document with SQLite data
  virtual int BuildContent()
{
  XmlStream xml(http.content());

  // Keep track of how long the commands take to run
  DWORD now = GetTickCount();

  // write XML file declaration
  xml << prolog()
    << "\n<!--\n"
    "   sqlite-xml-server@sqlite-xml-server.googlecode.com\n"
    "   Homepage:  http://sqlite-xml-server.googlecode.com\n\n"
    "   Generation Time: " << http.Rfc1123_DateTime() << "\n"
    "   SQLite version: " << SQLITE_VERSION << "\n"
    "-->"
    << tag("SqliteXML")
      << tag("Request")
        << tag("Domain") << chardata() << "http://lrunit.net" << endtag()
        << tag("Action") << chardata() << http.path().c_str() + 1 << endtag()
        << tag("Database") << chardata() << http.req.params_["Database"] << endtag()
        << tag("Command") << chardata() << http.req.params_["Command"] << endtag()
      << endtag();

  // Default result is Unknown Action
  Summary();

  // Execute
  if ( http.path() == "/Execute" ) { ExecuteSql(xml); }
  // Query
  else if ( http.path() == "/Query" ) { QuerySql(xml); }
  // Ping
  else if ( http.path() == "/Ping" ) { PingSql(xml); }
  // Unknown action
  else
    { http.content().str(""); return -1; }


  int duration = GetTickCount() - now;

  xml
    << tag("Summary")
      << tag("Reporter") << chardata() << http.req.params_["Reporter"] << endtag()
      << tag("Status") << chardata() << http.req.params_["Status"] << endtag()
      << tag("Text") << chardata() << http.req.params_["Text"] << endtag()
      << tag("Rowcount") << chardata() << http.req.params_["Rowcount"] << endtag()
      << tag("Duration") << chardata() << duration << " milliseconds" << endtag()
//        << tag("Duration") << chardata() << params["Duration"] << endtag()
    << endtag()
    << endalltags();

  http.contentType("text/xml");
  return 200;
}

// Display the SQL command execution summary - XML style
void Summary(
    const std::string &reporter = "SculleryServer",
    const std::string &status = "Error",
    const std::string &text = "Unknown action",
    const int &rowcount = 0,
    const int &duration = 2)
{
  std::stringstream d;
  http.req.params_["Reporter"] = reporter;
  http.req.params_["Status"] = status;
  http.req.params_["Text"] = text;

  d << rowcount;
  http.req.params_["Rowcount"] = d.str();
  d.str(""); d << duration << " milliseconds";
  http.req.params_["Duration"] = d.str();
}


/// Execute a sql command
void ExecuteSql(XmlStream &xml)
{
  db::Sqlite sqlite;
  try
  {
    xml << tag("Result");

    sqlite.Open(http.req.params_["Database"]);
    sqlite.ExecuteSql(http.req.params_["Command"]/*, http.content() */ );
    Summary("SQLite", "Success", http.req.params_["Command"]);
  }
  catch (std::exception& e)
  {
    Summary("SQLite", "Error", e.what());
  }
    xml << endtag();
}

/// Query rows from database
void QuerySql(XmlStream &xml)
{
  db::Sqlite sqlite;
  try
  {
    xml << tag("Result");

    sqlite.Open(http.req.params_["Database"]);
    int rowcount = sqlite.LoadRecordsIntoXmlStream(http.req.params_["Command"], xml );
    Summary("SQLite", "Success", http.req.params_["Command"], rowcount);
  }
  catch (std::exception& e)
  {
    Summary("SQLite", "Error", e.what());
  }
    xml << endtag();

}

/// Ping the server- which just replies with a Pong
void PingSql(XmlStream &xml)
{
  db::Sqlite sqlite;

  xml << tag("Result")
    << tag("Ping") << chardata() << "Pong" << endtag()
  << endtag();

  Summary("SculleryServer", "Success", db::SQLiteCodes[0], 0);
}

}; // class Xml
// **********************************************************************


} // namespace srv
} // namespace web
} // namespace cdn

#endif

